package com.iwhalecloud.citybrain.flow.platform.manager.application.service.impl;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageInfo;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.enums.ProcessInstanceLinkStatusEnum;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.enums.TaskLinkStatusEnum;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.threadlocal.StartUserHolder;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.FlowSimpleService;
import com.iwhalecloud.citybrain.flow.platform.manager.common.ErrorCode;
import com.iwhalecloud.citybrain.flow.platform.manager.common.ResponseResult;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.FlowNodeParticipantSettingVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.FlowParticipantInfoVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.FlowProcessInstanceTraceVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.ModelQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.NodeParticipantItem;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.NodeParticipantSetting;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.Pageable;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.SimpleCompleteTaskResult;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.SimpleMyApplyList;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.SimpleMyApplyQuery;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.SimpleStartProcessSetting;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.SimpleTaskList;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.SimpleTaskQuery;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.User;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.model.ModelSimpleVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.monitor.FlowTaskTraceVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.monitor.HttpProcessInstanceQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.monitor.HttpTaskQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.task.TaskHandleVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowModelRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowNodeParticipantSettingRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowParticipantRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowProcessInstanceTraceRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowTaskTraceRepository;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.common.impl.identity.Authentication;
import org.flowable.engine.runtime.ProcessInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author lilh
 * @date 2021/1/6 下午1:53
 */
@Service
public class FlowSimpleServiceImpl implements FlowSimpleService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private FlowModelRepository modelRepository;

    @Autowired
    private FlowNodeParticipantSettingRepository nodeParticipantSettingRepository;

    @Autowired
    private FlowParticipantRepository participantRepository;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private FlowTaskTraceRepository taskTraceRepository;

    @Autowired
    private FlowProcessInstanceTraceRepository processInstanceTraceRepository;

    @Autowired
    private TaskService taskService;

    @Override
    public ResponseResult<List<ModelSimpleVo>> listModel() {
        return ResponseResult.success(modelRepository.listModel(new ModelQueryVo()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult<Boolean> setNodeParticipant(NodeParticipantSetting nodeParticipantSetting) {
        checkParam(nodeParticipantSetting);
        //1.更新原有的配置
        updateOldSettings(nodeParticipantSetting);
        if (CollectionUtils.isEmpty(nodeParticipantSetting.getNodeParticipantItems())) {
            logger.warn("参数不全");
            return ResponseResult.fail(ErrorCode.PARAMETER_ERROR.errCode(), "参数不全");
        }

        //2.新增记录
        addNewSetting(nodeParticipantSetting);
        return ResponseResult.success(true);
    }

    @Override
    public ResponseResult<String> startProcessInstance(SimpleStartProcessSetting startProcessSetting) {
        checkParam(startProcessSetting);
        try {
            StartUserHolder.setUser(resolveStarter(startProcessSetting));
            Authentication.setAuthenticatedUserId(startProcessSetting.getParticipantId());
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(startProcessSetting.getModelKey(), "isFromSdkCall", startProcessSetting.getParams());
            if (Objects.nonNull(processInstance)) {
                return ResponseResult.success(processInstance.getProcessInstanceId());
            }
            return ResponseResult.fail(ErrorCode.CREATE_PROCESS_INSTANCE_FAILED.errCode(), ErrorCode.CREATE_PROCESS_INSTANCE_FAILED.errMessage());
        } finally {
            StartUserHolder.remove();
        }
    }

    @Override
    public ResponseResult<List<SimpleTaskList>> queryTask(SimpleTaskQuery simpleTaskQuery) {
        checkParam(simpleTaskQuery);
        if (StringUtils.isBlank(simpleTaskQuery.getType())) {
            simpleTaskQuery.setType(TaskLinkStatusEnum.TODO.getCode());
        }
        if (!isValidType(simpleTaskQuery)) {
            throw new IllegalArgumentException("type参数取值错误，只能为['TODO','DONE']");
        }
        processPageParams(simpleTaskQuery);
        return doQuery(simpleTaskQuery);
    }

    @Override
    public ResponseResult<List<SimpleMyApplyList>> queryMyApply(SimpleMyApplyQuery myApplyQuery) {
        checkParam(myApplyQuery);
        processPageParams(myApplyQuery);
        return doQuery(myApplyQuery);
    }

    @Override
    public ResponseResult<Boolean> terminateProcessInstance(String processInstanceId) {
        Assert.notEmpty(processInstanceId);
        FlowProcessInstanceTraceVo pt = processInstanceTraceRepository.getProcessInstanceTraceByProcessInstanceId(processInstanceId);
        if (Objects.isNull(pt)) {
            return ResponseResult.fail(ErrorCode.PROCESS_INSTANCE_NOT_EXIST.errCode(), ErrorCode.PROCESS_INSTANCE_NOT_EXIST.errMessage());
        }
        runtimeService.deleteProcessInstance(processInstanceId, "basic-驳回终止");
        return ResponseResult.success(true);
    }

    @Override
    public ResponseResult<SimpleCompleteTaskResult> completeTask(TaskHandleVo taskHandleVo) {
        checkParam(taskHandleVo);
        FlowTaskTraceVo taskTrace = taskTraceRepository.getTaskTraceByTaskId(taskHandleVo.getTaskId());
        if (Objects.isNull(taskTrace) || Objects.nonNull(taskTrace.getCompleteTime())) {
            return ResponseResult.fail(ErrorCode.WORK_ITEM_NOT_EXIST.errCode(), "任务不存在或者已完成");
        }
        if (MapUtil.isNotEmpty(taskHandleVo.getVariables())) {
            taskService.setVariablesLocal(taskHandleVo.getTaskId(), taskHandleVo.getVariables());
        }
        taskService.complete(taskTrace.getTaskId(), taskHandleVo.getVariables());
        FlowProcessInstanceTraceVo pit = processInstanceTraceRepository.getProcessInstanceTraceByProcessInstanceId(taskTrace.getProcessInstanceId());
        SimpleCompleteTaskResult result = new SimpleCompleteTaskResult();
        result.setProcessInstanceCompleted(Objects.equals(ProcessInstanceLinkStatusEnum.END.getCode(), pit.getLinkStatus()));
        return ResponseResult.success(result);
    }

    private void checkParam(TaskHandleVo taskHandleVo) {
        Assert.notNull(taskHandleVo);
        Assert.notEmpty(taskHandleVo.getTaskId());
    }

    private ResponseResult<List<SimpleMyApplyList>> doQuery(SimpleMyApplyQuery myApplyQuery) {
        HttpProcessInstanceQueryVo processInstanceQueryVo = new HttpProcessInstanceQueryVo();
        BeanUtils.copyProperties(myApplyQuery, processInstanceQueryVo);
        List<FlowProcessInstanceTraceVo> list = processInstanceTraceRepository.list(processInstanceQueryVo);
        PageInfo<FlowProcessInstanceTraceVo> pageTasks = new PageInfo<>(list);
        return ResponseResult.success(
                list.stream().map(item -> {
                    SimpleMyApplyList tmp = new SimpleMyApplyList();
                    BeanUtils.copyProperties(item, tmp);
                    return tmp;
                }).collect(Collectors.toList())
        ).setPageSize(myApplyQuery.getPageSize()).setPageNum(myApplyQuery.getPageNum())
                .setTotalCount((int) pageTasks.getTotal());
    }

    private void checkParam(SimpleMyApplyQuery myApplyQuery) {
        Assert.notNull(myApplyQuery);
        Assert.notEmpty(myApplyQuery.getStarterId());
    }

    private ResponseResult<List<SimpleTaskList>> doQuery(SimpleTaskQuery simpleTaskQuery) {

        HttpTaskQueryVo taskQueryVo = new HttpTaskQueryVo();
        BeanUtils.copyProperties(simpleTaskQuery, taskQueryVo);
        List<FlowTaskTraceVo> tasks = taskTraceRepository.listTaskTraces(taskQueryVo);
        PageInfo<FlowTaskTraceVo> pageTasks = new PageInfo<>(tasks);
        return ResponseResult.success(
                tasks.stream().map(item -> {
                    SimpleTaskList tmp = new SimpleTaskList();
                    BeanUtils.copyProperties(item, tmp);
                    return tmp;
                }).collect(Collectors.toList())
        ).setPageSize(simpleTaskQuery.getPageSize())
                .setPageNum(simpleTaskQuery.getPageNum())
                .setTotalCount((int) pageTasks.getTotal());
    }

    protected void processPageParams(Pageable pageable) {
        if (Objects.isNull(pageable.getPageNum()) || pageable.getPageNum() <= 0) {
            pageable.setPageNum(1);
        }

        if (Objects.isNull(pageable.getPageSize()) || pageable.getPageSize() <= 0) {
            pageable.setPageSize(10);
        }

        //单次最多查询100条
        if (pageable.getPageSize() > 100) {
            pageable.setPageSize(100);
        }
    }

    private boolean isValidType(SimpleTaskQuery simpleTaskQuery) {
        return Objects.equals(simpleTaskQuery.getType(), TaskLinkStatusEnum.TODO.getCode())
                || Objects.equals(simpleTaskQuery.getType(), TaskLinkStatusEnum.DONE.getCode());
    }

    private void checkParam(SimpleTaskQuery simpleTaskQuery) {
        Assert.notEmpty(simpleTaskQuery.getAssignee());
    }

    private User resolveStarter(SimpleStartProcessSetting startProcessSetting) {
        User starter = new User();
        starter.setUserId(startProcessSetting.getParticipantId());
        starter.setUserCode(startProcessSetting.getParticipantId());
        starter.setUserName(startProcessSetting.getParticipantName());
        return starter;
    }

    private void checkParam(SimpleStartProcessSetting startProcessSetting) {
        Assert.notNull(startProcessSetting);
        Assert.notEmpty(startProcessSetting.getModelKey());
        Assert.notEmpty(startProcessSetting.getParticipantId());
        Assert.notEmpty(startProcessSetting.getParticipantName());
    }

    private void addNewSetting(NodeParticipantSetting nodeParticipantSetting) {
        for (NodeParticipantItem item : nodeParticipantSetting.getNodeParticipantItems()) {
            addSetting(nodeParticipantSetting, item);
            addParticipant(item);
        }
    }

    private void addParticipant(NodeParticipantItem item) {
        FlowParticipantInfoVo participant = new FlowParticipantInfoVo();
        participant.setParticipantId(item.getParticipantId());
        participant.setParticipantName(item.getParticipantName());
        participant.setParticipantType("STA");
        participantRepository.insertParticipant(participant);
    }

    private void addSetting(NodeParticipantSetting nodeParticipantSetting, NodeParticipantItem item) {
        FlowNodeParticipantSettingVo tmp = new FlowNodeParticipantSettingVo();
        tmp.setModelKey(nodeParticipantSetting.getModelKey());
        tmp.setNodeCode(nodeParticipantSetting.getModelKey() + "_" + item.getNodeCode().getCode());
        tmp.setParticipantId(item.getParticipantId());
        tmp.setParticipantName(item.getParticipantName());
        tmp.setState("Y");
        nodeParticipantSettingRepository.insert(tmp);
    }

    private boolean updateOldSettings(NodeParticipantSetting nodeParticipantSetting) {
        List<NodeParticipantItem> nodeParticipantItems = nodeParticipantSetting.getNodeParticipantItems().stream().filter(item -> (Objects.nonNull(item.getNodeCode()) && StrUtil.isNotBlank(item.getParticipantId()))).collect(Collectors.toList());
        nodeParticipantSetting.setNodeParticipantItems(nodeParticipantItems);
        if (CollectionUtils.isNotEmpty(nodeParticipantItems)) {
            nodeParticipantSettingRepository.deleteSetting(nodeParticipantSetting.getModelKey(), nodeParticipantItems.stream().map(item -> nodeParticipantSetting.getModelKey() + "_" + item.getNodeCode().getCode()).collect(Collectors.toList()));
            return true;
        }
        return false;
    }

    private void checkParam(NodeParticipantSetting nodeParticipantSetting) {
        Assert.notNull(nodeParticipantSetting);
        Assert.notNull(nodeParticipantSetting.getModelKey());
        Assert.notEmpty(nodeParticipantSetting.getNodeParticipantItems());
    }
}
